classdef MainDeviceController < handle
    %MAINDEVICECONTROLLER Summary of this class goes here
    %   Detailed explanation goes here
    
    properties
        session
        ziMaster
        ziSlave
    end
    
    properties( Access = {?NMRGyroscope.ParametricMagnetometer.RubidiumParametricMagnetometer, ?NMRGyroscope.NMRSystem.XeNMRSystem} )
        CurrentSettings
        ModSettings
        DemodSettings
        PIDSettings
        Conversions
    end
    
    methods
        function obj = MainDeviceController(session)
            %MAINDEVICECONTROLLER Construct an instance of this class
            %   Detailed explanation goes here
            L = log4m.getLogger();
            
            obj.session = session;
            obj.ziMaster = session.getDevice(Constant.ZIMasterName);
            obj.ziSlave = session.getDevice(Constant.ZISlaveName);
            
            obj.LoadMachineSettings();
            obj.LoadDefaultValues();

            L.trace('MainDeviceController', 'MainDeviceController object created.');
        end
    end
    %% Public Methods Defined Separatedly
    methods
        LoadMachineSettings(obj);
        LoadDefaultValues(obj);
    end
    %% Machine State and Demod Channel
    methods
        function obj = SwitchMachineState(obj, state)
            p=inputParser();
            stateCheck = @(x) ismember(x, {'init'});
            p.addRequired('state', stateCheck);
            p.parse(state);
            
            switch state
                case 'init'
                    obj.LoadMachineSettings();
                    obj.unLockMagnetometerTransverseFieldY();
                    obj.unLockMagnetometerTransverseFieldZ();
                    obj.unLockXe129Frequency();
                    obj.unLockXe131Frequency();
                    obj.disableNMRDriving129();
                    obj.disableNMRDriving131();                    
                    obj.SetMagnetomiterDemodChannelState('standard');
                otherwise
            end
            obj.syncDevice();
        end
        
        function obj = SetMagnetomiterDemodChannelState(obj, state)
            p=inputParser();
            stateCheck = @(x) ismember(x, {'standard', 'scanWP'});
            p.addRequired('state', stateCheck);
            p.parse(state);
            
            switch state
                case 'standard'
                    obj.setMagnetometer_ByDemodFastChannel(); % using default values 
                    obj.setMagnetometer_ByDemodSlowChannel();
                    obj.setMagnetometer_BzDemodSlowChannel();
                case 'scanWP'
                    obj.setMagnetometer_ByDemodSlowChannel('bandwidth', obj.DemodSettings.ByDemodSlow.bandwidth1);
                    obj.setMagnetometer_BzDemodSlowChannel('bandwidth', obj.DemodSettings.ByDemodSlow.bandwidth1);
                otherwise
            end
            obj.syncDevice();
        end
        
        function obj = syncDevice(obj)
            obj.ziMaster.sync();
            obj.ziSlave.sync();
        end
        
        function obj = SetNMRDemodChannelState(obj, state)
            p=inputParser();
            stateCheck = @(x) ismember(x, {'standard'});
            p.addRequired('state', stateCheck);
            p.parse(state);
            
            switch state
                case 'standard'
                    obj.setXe129DemodChannel();
                    obj.setXe131DemodChannel();
                otherwise
            end
            obj.syncDevice();
        end
        
    end
    %% Settings Interface
    methods
        function DispSettings(obj)
            obj.CurrentControlSettings();
            obj.ModulationSettings();
            obj.DemodulationSettings();
            obj.FeedbackControlSettings();
            obj.ConversionCoefficientSettings();
        end
        function settings = getAllSettings(obj)            
            settings.CurrentSettings = obj.CurrentSettings;
            settings.ModSettings = obj.ModSettings;
            settings.DemodSettings = obj.DemodSettings;
            settings.PIDSettings = obj.PIDSettings;
            settings.Conversions = obj.Conversions;
        end
        function CurrentControlSettings(obj, varargin)
            if nargin == 1
                obj.disp_settings('CurrentSettings');
            elseif nargin < 4 
                obj.disp_settings('CurrentSettings', varargin{:});
            elseif nargin == 4
                obj.change_settings('CurrentSettings', varargin{:});
            end
        end
        function ModulationSettings(obj, varargin)
            if nargin == 1
                obj.disp_settings('ModSettings');
            elseif nargin < 4 
                obj.disp_settings('ModSettings', varargin{:});
            elseif nargin == 4
                obj.change_settings('ModSettings', varargin{:});
            end
        end
        function DemodulationSettings(obj, varargin)
            if nargin == 1
                obj.disp_settings('DemodSettings');
            elseif nargin < 4 
                obj.disp_settings('DemodSettings', varargin{:});
            elseif nargin == 4
                obj.change_settings('DemodSettings', varargin{:});
            end
        end
        function FeedbackControlSettings(obj, varargin)
            if nargin == 1
                obj.disp_settings('PIDSettings');
            elseif nargin < 4 
                obj.disp_settings('PIDSettings', varargin{:});
            elseif nargin == 4
                obj.change_settings('PIDSettings', varargin{:});
            end
        end
        function ConversionCoefficientSettings(obj, varargin)
            if nargin == 1
                obj.disp_settings('Conversions');
            elseif nargin < 4 
                obj.disp_settings('Conversions', varargin{:});
            elseif nargin == 4
                obj.change_settings('Conversions', varargin{:});
            end
        end
    end
        
    %% Parametric Magnetometer Control
    methods
        % Current control
        function obj = setCurrentControlVoltX(obj, volt)
            L=log4m.getLogger;
            
            if nargin == 1
                volt = obj.CurrentSettings.BxCtrlDC.volt;
            end
            
            ch = obj.CurrentSettings.BxCtrlDC.index;
            
            v0 = obj.getCurrentControlVoltX();
            dv = min( abs(volt-v0), volt/1000.0);
            dv = sign(volt-v0)*dv;
            
            for v = v0:dv:volt
                obj.ziSlave.setAuxOutOffset(ch, v);
                pause(0.01);
            end
            
            L.info('MainDeviceController::setMainCurrent', ...
                   sprintf('Current control X set to %5.3f V.', obj.getCurrentControlVoltX() ));
        end
        function val = getCurrentControlVoltX(obj)
            L=log4m.getLogger;
            ch = obj.CurrentSettings.BxCtrlDC.index;
            val = obj.ziSlave.getAuxOutOffset(ch);
            L.info('MainDeviceController::getCurrentControlVoltX', ...
                   sprintf('Current control Volt X = %5.3f V.', val ));
        end
        
        function obj = setCurrentControlVoltY(obj, volt)
            L=log4m.getLogger;
            if nargin == 1
                volt = obj.CurrentSettings.ByCtrlDC.volt;
            end
            ch = obj.CurrentSettings.ByCtrlDC.index;
            obj.ziMaster.setAuxOutOffset(ch, volt);
            L.info('MainDeviceController::setCurrentControlVoltY', ...
                   sprintf('Current control Y set to  %5.3f V.', obj.getCurrentControlVoltY() ));
        end
        function val = getCurrentControlVoltY(obj)
            L=log4m.getLogger;
            ch = obj.CurrentSettings.ByCtrlDC.index;
            val = obj.ziMaster.getAuxOutOffset(ch);
            L.info('MainDeviceController::getCurrentControlVoltY', ...
                   sprintf('Current control Volt Y = %5.3f V.', val ));
        end
        
        function obj = setCurrentControlVoltZ(obj, volt)
            L=log4m.getLogger;
            if nargin == 1
                volt = obj.CurrentSettings.BzCtrlDC.volt;
            end
            ch = obj.CurrentSettings.BzCtrlDC.index;
            obj.ziMaster.setAuxOutOffset(ch, volt);
            L.info('MainDeviceController::setCurrentControlVoltZ', ...
                   sprintf('Current control Z set to  %5.3f V.', obj.getCurrentControlVoltZ() ));
        end        
        function val = getCurrentControlVoltZ(obj)
            L=log4m.getLogger;
            ch = obj.CurrentSettings.BzCtrlDC.index;
            val = obj.ziMaster.getAuxOutOffset(ch);
            L.info('MainDeviceController::getCurrentControlVoltZ', ...
                   sprintf('Current control Volt Z = %5.3f V.', val ));
        end
        
        % Modulations
        function obj = enableMagnetometerMainModulation(obj)
            obj.ziMaster.fireSigOut();
        end
        function obj = disableMagnetometerMainModulation(obj)
            obj.ziMaster.silenceSigOut();
        end
        function obj = enableMagnetometerModulateAmplitude(obj)
            ch = obj.ModSettings.RbModulation.index;
            obj.ziMaster.enableOutAmplitude(ch);
        end
        
        function obj = setMagnetometerModulationFrequency(obj, freq)
            ch = obj.ModSettings.RbModulation.index;
            obj.ziMaster.setOscFreq(ch, freq); pause(1.0);
        end
        function val = getMagnetometerModulationFrequency(obj)
            ch = obj.ModSettings.RbModulation.index;
            val = obj.ziMaster.getOscFreq(ch);
        end
        
        function obj = setMagnetometerModulationStrength(obj, volt)
            ch = obj.ModSettings.RbModulation.index;
            obj.ziMaster.setSigOutAmplitude(ch, volt); pause(1.0);   
        end
        function val = getMagnetometerModulationStrength(obj)
            ch = obj.ModSettings.RbModulation.index;
            val = obj.ziMaster.getSigOutAmplitude(ch);   
        end
        
        function obj = enableMagnetometerCalibrationSignal(obj)
            ch = obj.ModSettings.CalibrationModulation.index;
            freq = obj.ModSettings.CalibrationModulation.frequency;
            amp = obj.ModSettings.CalibrationModulation.amplitude;
            obj.ziSlave.setOscFreq(ch, freq).setSigOutAmplitude(ch, amp);
            obj.ziSlave.setSigOutOscSelect(ch, ch);
            obj.ziSlave.enableOutAmplitude(ch); pause(1.0);
            obj.setMangetometerDemodZeroPhaseY();
        end
        
        function obj = disableMagnetometerCalibrationSignal(obj)
            ch = obj.ModSettings.CalibrationModulation.index;
            obj.ziSlave.disableOutAmplitude(ch); pause(1.0);
        end
        
        % Demodulation
        function obj = setMagnetometer_ByDemodFastChannel(obj, varargin)
            p=inputParser();            
            p.addParameter('order',     obj.DemodSettings.ByDemodFast.order, @(x) ismember(x, 1:8));
            p.addParameter('bandwidth', obj.DemodSettings.ByDemodFast.bandwidth, @(x) isscalar(x) && x > 0);
            p.addParameter('rate',      obj.DemodSettings.ByDemodFast.rate, @(x) isscalar(x) && x > 0);
            p.parse(varargin{:});
            
            ch = obj.DemodSettings.ByDemodFast.index;
            obj.ziMaster.setDemodFilterOrder(ch, p.Results.order);
            obj.ziMaster.setDemodFilterBW(ch, p.Results.bandwidth);
            obj.ziMaster.setDataTransferRate(ch, p.Results.rate);
            obj.ziMaster.enableDataTransfer(ch);
        end
        function obj = setMagnetometer_ByDemodSlowChannel(obj, varargin)
            p=inputParser();            
            p.addParameter('order',     obj.DemodSettings.ByDemodSlow.order, @(x) ismember(x, 1:8));
            p.addParameter('bandwidth', obj.DemodSettings.ByDemodSlow.bandwidth, @(x) isscalar(x) && x > 0);
            p.addParameter('rate',      obj.DemodSettings.ByDemodSlow.rate, @(x) isscalar(x) && x > 0);
            p.parse(varargin{:});
            
            ch = obj.DemodSettings.ByDemodSlow.index;
            obj.ziMaster.setDemodFilterOrder(ch, p.Results.order);
            obj.ziMaster.setDemodFilterBW(ch, p.Results.bandwidth);
            obj.ziMaster.setDataTransferRate(ch, p.Results.rate);
            obj.ziMaster.enableDataTransfer(ch);
        end
        function obj = setMagnetometer_BzDemodSlowChannel(obj, varargin)
            p=inputParser();            
            p.addParameter('order',     obj.DemodSettings.BzDemodSlow.order, @(x) ismember(x, 1:8));
            p.addParameter('bandwidth', obj.DemodSettings.BzDemodSlow.bandwidth, @(x) isscalar(x) && x > 0);
            p.addParameter('rate',      obj.DemodSettings.BzDemodSlow.rate, @(x) isscalar(x) && x > 0);
            p.parse(varargin{:});
            
            ch = obj.DemodSettings.BzDemodSlow.index;
            obj.ziMaster.setDemodFilterOrder(ch, p.Results.order);
            obj.ziMaster.setDemodFilterBW(ch, p.Results.bandwidth);
            obj.ziMaster.setDataTransferRate(ch, p.Results.rate);
            obj.ziMaster.enableDataTransfer(ch);
        end
        
        function obj = setMagnetometerDemodPhaseY(obj, phase)
            ch1 = obj.DemodSettings.ByDemodSlow.index;
            ch2 = obj.DemodSettings.ByDemodFast.index;
            obj.ziMaster.setDemodPhase(ch1, phase);
            obj.ziMaster.setDemodPhase(ch2, phase);
        end
        function obj = setMagnetometerDemodPhaseZ(obj, phase)
            ch = obj.DemodSettings.BzDemodSlow.index;
            obj.ziMaster.setDemodPhase(ch, phase);
        end
        function obj = setMangetometerDemodZeroPhaseY(obj)
            ch = obj.DemodSettings.ByDemodFast.index;
            obj.ziMaster.DemodPhaseZero(ch);
        end
        function obj = setCalibrationDemodulationPhase(obj, phase)
            ch = obj.DemodSettings.CalibrationDemod.index;
            obj.ziMaster.setDemodPhase(ch, phase);
        end
        function obj = setCalibrationDemodulationRate(obj)
            ch = obj.DemodSettings.CalibrationDemod.index;
            rate = obj.DemodSettings.CalibrationDemod.rate;
            obj.ziMaster.setDataTransferRate(ch, rate);
        end
        
        function rate = getMagnetometerFastDemodRate(obj)
            ch = obj.DemodSettings.ByDemodFast.index;
            rate = obj.ziMaster.getDataTransferRate(ch);
        end
        function obj = setMagnetometerFastDemodRate(obj, rate)
            ch = obj.DemodSettings.ByDemodFast.index;
            if nargin == 1
                rate = obj.DemodSettings.ByDemodFast.rate;
            end
            obj.ziMaster.setDataTransferRate(ch, rate);
        end
        % Feedback/PID Lock
        function obj = lockMagnetometerTransverseFieldY(obj, varargin)
            L = log4m.getLogger();
            ch = obj.PIDSettings.ByLock.index;
            auxVolt = obj.getCurrentControlVoltY();
            
            p=inputParser;
            pCheck = @(x) x >= 0 && x < 100;
            iCheck = @(x) x >= 0 && x < 1e4;
            dCheck = @(x) x >= 0 && x < 1e-2;
            dLimCheck = @(x) x > 0;
            rateCheck = @(x) x > 0;
            p.addParameter('order', obj.PIDSettings.ByLock.order, @(x) ismember(x, 1:8) );
            p.addParameter('bandwidth', obj.PIDSettings.ByLock.bandwidth, @(x) isscalar(x) && x>0 );
            p.addParameter('harmonic', obj.PIDSettings.ByLock.harmonic, @(x) isscalar(x) && x>0 );
            
            p.addParameter('p', obj.PIDSettings.ByLock.p, pCheck);
            p.addParameter('i', obj.PIDSettings.ByLock.i, iCheck);
            p.addParameter('d', obj.PIDSettings.ByLock.d, dCheck);
            p.addParameter('dLim', obj.PIDSettings.ByLock.dlim, dLimCheck);
            p.addParameter('rate', obj.PIDSettings.ByLock.rate, rateCheck);
            p.addParameter('gain', 1.0, @(x) isscalar(x));% && x > 0);
            p.addParameter('center', auxVolt, @(x) isscalar(x) && x > -10 && x < 10);
            p.parse(varargin{:});
            
            obj.ziMaster.disablePIDPhaseUnwarp(ch); pause(0.2);
            obj.ziMaster.setPIDFilterOrder(ch, p.Results.order); pause(0.2);
            obj.ziMaster.setPIDFilterBW(ch, p.Results.bandwidth); pause(0.2);
            obj.ziMaster.setPIDHarmonic(ch, p.Results.harmonic); pause(0.2);

            obj.ziMaster.setPIDCenter(ch, p.Results.center);
            obj.ziMaster.setPValue(ch, p.Results.p/p.Results.gain);
            obj.ziMaster.setIValue(ch, p.Results.i/p.Results.gain);
            obj.ziMaster.setDValue(ch, p.Results.d/p.Results.gain);
            obj.ziMaster.setDLimValue(ch, p.Results.dLim);
            obj.ziMaster.setRateValue(ch, p.Results.rate);
            obj.ziMaster.lock(ch);
            L.info('MainDeviceController::lockMagnetometerTransverseFieldY', 'TransverseField Y locked.');
        end
        function obj = unLockMagnetometerTransverseFieldY(obj)
            L = log4m.getLogger();
            ch = obj.PIDSettings.ByLock.index;
            obj.ziMaster.unLock(ch);
            L.info('MainDeviceController::unLockMagnetometerTransverseFieldY', 'TransverseField Y lock released.');
        end
        
        function obj = lockMagnetometerTransverseFieldZ(obj, varargin)
            L=log4m.getLogger();
            ch = obj.PIDSettings.BzLock.index;
            auxVolt = obj.getCurrentControlVoltZ();
            
            p=inputParser;
            pCheck = @(x) x >= 0 && x < 100;
            iCheck = @(x) x >= 0 && x < 1e4;
            dCheck = @(x) x >= 0 && x < 1e-2;
            dLimCheck = @(x) x > 0;
            rateCheck = @(x) x > 0;
            p.addParameter('order', obj.PIDSettings.BzLock.order, @(x) ismember(x, 1:8) );
            p.addParameter('bandwidth', obj.PIDSettings.BzLock.bandwidth, @(x) isscalar(x) && x>0 );
            p.addParameter('harmonic', obj.PIDSettings.BzLock.harmonic, @(x) isscalar(x) && x>0 );
            
            p.addParameter('p', obj.PIDSettings.BzLock.p, pCheck);
            p.addParameter('i', obj.PIDSettings.BzLock.i, iCheck);
            p.addParameter('d', obj.PIDSettings.BzLock.d, dCheck);
            p.addParameter('dLim', obj.PIDSettings.BzLock.dlim, dLimCheck);
            p.addParameter('rate', obj.PIDSettings.BzLock.rate, rateCheck);
            p.addParameter('gain', 1.0, @(x) isscalar(x));% && x > 0);
            p.addParameter('center', auxVolt, @(x) isscalar(x) && x > -10 && x < 10);
            p.parse(varargin{:});
            
            obj.ziMaster.disablePIDPhaseUnwarp(ch); pause(0.2);
            obj.ziMaster.setPIDFilterOrder(ch, p.Results.order); pause(0.2);
            obj.ziMaster.setPIDFilterBW(ch, p.Results.bandwidth); pause(0.2);
            obj.ziMaster.setPIDHarmonic(ch, p.Results.harmonic); pause(0.2);
            
            obj.ziMaster.setPIDCenter(ch, p.Results.center);
            obj.ziMaster.setPValue(ch, p.Results.p/p.Results.gain);
            obj.ziMaster.setIValue(ch, p.Results.i/p.Results.gain);
            obj.ziMaster.setDValue(ch, p.Results.d/p.Results.gain);
            obj.ziMaster.setDLimValue(ch, p.Results.dLim);
            obj.ziMaster.setRateValue(ch, p.Results.rate);
            obj.ziMaster.lock(ch);
            L.info('MainDeviceController::lockMagnetometerTransverseFieldZ', 'TransverseField Z locked.');
        end
        function obj = unLockMagnetometerTransverseFieldZ(obj)
            L = log4m.getLogger();
            ch = obj.PIDSettings.BzLock.index;
            obj.ziMaster.unLock(ch);
            L.info('MainDeviceController::unLockMagnetometerTransverseFieldY', 'TransverseField Y lock released.');
        end
        
        function obj = enableSelfOscillation(obj, varargin)
            p=inputParser;
            p.addParameter('ratio', -1, @isnumeric);
            p.parse(varargin{:});
            
            ch = obj.CurrentSettings.BzCtrlDC.index;            
            chDemod = obj.DemodSettings.BzDemodSlow.index;            
            
            obj.unLockMagnetometerTransverseFieldZ(); pause(1);           
            obj.ziMaster.setDemodFilterOrder(chDemod, 4); pause(1);
            obj.ziMaster.setDemodFilterBW(chDemod, 1000.0); pause(1);
            
            obj.ziMaster.setAuxOutSelect(ch, 0); pause(1);
            obj.ziMaster.setAuxOutScal(ch, p.Results.ratio); pause(30);
            obj.ziMaster.setAuxOutSelect(ch, -1);
        end
        
        function obj = disableSelfOscillation(obj)
            ch = obj.CurrentSettings.BzCtrlDC.index;
            offset = obj.CurrentSettings.BzCtrlDC.volt;
            obj.ziMaster.setAuxOutSelect(ch, -1);
            obj.ziMaster.setAuxOutOffset(ch, offset);
            obj.ziMaster.setAuxOutScal(ch, 1);
            obj.lockMagnetometerTransverseFieldZ();
        end
    end
    
    %% NMR System Control
    methods
        % Current control
        
        % Modulations
        function obj = enableNMRDriving(obj)
            obj.ziSlave.fireSigOut();
        end
        
        function obj = disableNMRDriving(obj)
            obj.ziSlave.silenceSigOut();
        end
        
        function obj = setNMRDrivingOffset(obj, offset)
            obj.ziSlave.setSigOutOffset(offset);
        end
        
        function obj = enableNMRDriving129(obj)
            ch = obj.ModSettings.Xe129Modulation.index;
            obj.ziSlave.enableOutAmplitude(ch);
        end
        function obj = disableNMRDriving129(obj)
            ch = obj.ModSettings.Xe129Modulation.index;
            obj.ziSlave.disableOutAmplitude(ch);
        end
        function obj = enableNMRDriving131(obj)
            ch = obj.ModSettings.Xe131Modulation.index;
            obj.ziSlave.enableOutAmplitude(ch);
        end
        function obj = disableNMRDriving131(obj)
            ch = obj.ModSettings.Xe131Modulation.index;
            obj.ziSlave.disableOutAmplitude(ch);
        end
        function obj = setNMRDrivingAmplitude129(obj, amplitude)
            ch = obj.ModSettings.Xe129Modulation.index;
            obj.ziSlave.setSigOutAmplitude(ch, amplitude);
        end
        function obj = setNMRDrivingAmplitude131(obj, amplitude)
            ch = obj.ModSettings.Xe131Modulation.index;
            obj.ziSlave.setSigOutAmplitude(ch, amplitude);
        end
        function val = getNMRDrivingAmplitude129(obj)
            ch = obj.ModSettings.Xe129Modulation.index;
            val = obj.ziSlave.getSigOutAmplitude(ch);
        end
        function val = getNMRDrivingAmplitude131(obj)
            ch = obj.ModSettings.Xe131Modulation.index;
            val = obj.ziSlave.getSigOutAmplitude(ch);
        end
        
        function obj = setNMRDrivingFrequency129(obj, frequency)
            ch = obj.ModSettings.Xe129Modulation.index;
            obj.ziSlave.setOscFreq(ch, frequency);
        end
        function obj = setNMRDrivingFrequency131(obj, frequency)
            ch = obj.ModSettings.Xe131Modulation.index;
            obj.ziSlave.setOscFreq(ch, frequency);
        end
        function val = getNMRDrivingFrequency129(obj)
            ch = obj.ModSettings.Xe129Modulation.index;
            val = obj.ziSlave.getOscFreq(ch);
        end
        function val = getNMRDrivingFrequency131(obj)
            ch = obj.ModSettings.Xe131Modulation.index;
            val = obj.ziSlave.getOscFreq(ch);
        end
        
        % Demodulation
        function obj = setXe129DemodChannel(obj, varargin)
            p=inputParser();            
            p.addParameter('order',     obj.DemodSettings.Xe129Demod.order, @(x) ismember(x, 1:8));
            p.addParameter('bandwidth', obj.DemodSettings.Xe129Demod.bandwidth, @(x) isscalar(x) && x > 0);
            p.addParameter('rate',      obj.DemodSettings.Xe129Demod.rate, @(x) isscalar(x) && x > 0);
            p.parse(varargin{:});
            
            obj.setNMRXe129DemodOrder(p.Results.order);
            obj.setNMRXe129DemodBandWidth(p.Results.bandwidth);
            obj.setNMRXe129DataTransferRate(p.Results.rate);
            obj.enableXe129DataTransfer();
        end
        function obj = setXe131DemodChannel(obj, varargin)
            p=inputParser();
            p.addParameter('order',     obj.DemodSettings.Xe131Demod.order, @(x) ismember(x, 1:8));
            p.addParameter('bandwidth', obj.DemodSettings.Xe131Demod.bandwidth, @(x) isscalar(x) && x > 0);
            p.addParameter('rate',      obj.DemodSettings.Xe131Demod.rate, @(x) isscalar(x) && x > 0);
            p.parse(varargin{:});
                        
            obj.setNMRXe131DemodOrder(p.Results.order);
            obj.setNMRXe131DemodBandWidth(p.Results.bandwidth);
            obj.setNMRXe131DataTransferRate(p.Results.rate);
            obj.enableXe131DataTransfer();
        end
        
        function obj = setNMRZeroPhase129(obj)
            ch = obj.DemodSettings.Xe129Demod.index;
            obj.ziSlave.DemodPhaseZero(ch);
        end
        function obj = setNMRZeroPhase131(obj)
            ch = obj.DemodSettings.Xe131Demod.index;
            obj.ziSlave.DemodPhaseZero(ch);
        end
        
        function obj = setNMRXe129DemodOrder(obj, order)
            ch = obj.DemodSettings.Xe129Demod.index;
            obj.ziSlave.setDemodFilterOrder(ch, order);
        end
        function obj = setNMRXe131DemodOrder(obj, order)
            ch = obj.DemodSettings.Xe131Demod.index;
            obj.ziSlave.setDemodFilterOrder(ch, order);
        end
        
        function obj = setNMRXe129DemodBandWidth(obj, bw)
            ch = obj.DemodSettings.Xe129Demod.index;
            obj.ziSlave.setDemodFilterBW(ch, bw);
        end
        function obj = setNMRXe131DemodBandWidth(obj, bw)
            ch = obj.DemodSettings.Xe131Demod.index;
            obj.ziSlave.setDemodFilterBW(ch, bw);
        end
        
        function val = getNMRXe129DemodPhase(obj)
            ch = obj.DemodSettings.Xe129Demod.index;
            val= obj.ziSlave.getDemodPhase(ch);
        end
        function val = getNMRXe131DemodPhase(obj)
            ch = obj.DemodSettings.Xe131Demod.index;
            val= obj.ziSlave.getDemodPhase(ch);
        end
        
        function obj = setNMRXe129DataTransferRate(obj, rate)
            ch = obj.DemodSettings.Xe129Demod.index;
            obj.ziSlave.setDataTransferRate(ch, rate);
        end
        
        function obj = setNMRXe131DataTransferRate(obj, rate)
            ch = obj.DemodSettings.Xe131Demod.index;
            obj.ziSlave.setDataTransferRate(ch, rate);
        end
        
        function val = getNMRXe129DataTransferRate(obj)
            ch = obj.DemodSettings.Xe129Demod.index;
            val = obj.ziSlave.getDataTransferRate(ch);
        end
        
        function val = getNMRXe131DataTransferRate(obj)
            ch = obj.DemodSettings.Xe131Demod.index;
            val = obj.ziSlave.getDataTransferRate(ch);
        end
        
        function obj = enableXe129DataTransfer(obj)
            ch = obj.DemodSettings.Xe129Demod.index;
            obj.ziSlave.enableDataTransfer(ch);
        end
        function obj = enableXe131DataTransfer(obj)
            ch = obj.DemodSettings.Xe131Demod.index;
            obj.ziSlave.enableDataTransfer(ch);
        end
        
        % Feedback/PID Lock
        function lockXe129Frequency(obj, pidSettings, varargin)
            p=inputParser();
            p.addParameter('dLim', obj.PIDSettings.Xe129Lock.dlim, @(x) x > 0);
            p.addParameter('rate', obj.PIDSettings.Xe129Lock.rate, @(x) x > 0);
            p.parse(varargin{:});
            
            ch = obj.PIDSettings.Xe129Lock.index;

            obj.ziSlave.disablePIDPhaseUnwarp(ch); pause(0.2);
            obj.ziSlave.setPIDFilterOrder(ch, pidSettings.order); pause(0.2);
            obj.ziSlave.setPIDFilterBW(ch, pidSettings.bandwidth); pause(0.2);
            obj.ziSlave.setPIDHarmonic(ch, obj.PIDSettings.Xe129Lock.harmonic); pause(0.2);
            
            obj.ziSlave.setPIDCenter(ch, obj.getNMRDrivingFrequency129() );
            obj.ziSlave.setPValue(ch, pidSettings.pValue);
            obj.ziSlave.setIValue(ch, pidSettings.iValue);
            obj.ziSlave.setDValue(ch, pidSettings.dValue);  
            obj.ziSlave.setDLimValue(ch, p.Results.dLim);
            obj.ziSlave.setRateValue(ch, p.Results.rate);
            
            obj.ziSlave.lock(ch);
        end
        function unLockXe129Frequency(obj)
            L = log4m.getLogger;
            ch = obj.PIDSettings.Xe129Lock.index;
            obj.ziSlave.unLock(ch);
            L.info('MainDeviceController::unLockXe129Frequency', 'Xe129 frequency lock released.');
        end
        function lockXe131Frequency(obj, pidSettings, varargin)
            p=inputParser();
            p.addParameter('dLim', obj.PIDSettings.Xe131Lock.dlim, @(x) x > 0);
            p.addParameter('rate', obj.PIDSettings.Xe131Lock.rate, @(x) x > 0);
            p.parse(varargin{:});
            
            ch = obj.PIDSettings.Xe131Lock.index;

            obj.ziSlave.disablePIDPhaseUnwarp(ch); pause(0.2);
            obj.ziSlave.setPIDFilterOrder(ch, pidSettings.order); pause(0.2);
            obj.ziSlave.setPIDFilterBW(ch, pidSettings.bandwidth); pause(0.2);
            obj.ziSlave.setPIDHarmonic(ch, obj.PIDSettings.Xe131Lock.harmonic); pause(0.2);
            
            obj.ziSlave.setPIDCenter(ch, obj.getNMRDrivingFrequency131() );
            obj.ziSlave.setPValue(ch, pidSettings.pValue);
            obj.ziSlave.setIValue(ch, pidSettings.iValue);
            obj.ziSlave.setDValue(ch, pidSettings.dValue);  
            obj.ziSlave.setDLimValue(ch, p.Results.dLim);
            obj.ziSlave.setRateValue(ch, p.Results.rate);
            
            obj.ziSlave.lock(ch);
        end
        function unLockXe131Frequency(obj)
            L = log4m.getLogger;
            ch = obj.PIDSettings.Xe131Lock.index;
            obj.ziSlave.unLock(ch);
            L.info('MainDeviceController::unLockXe131Frequency', 'Xe131 frequency lock released.');
        end
        
        function [center, pVal, iVal, dVal, DlimVal, rateVal, filterOrder, filterBW] = getXe129LockingParameter(obj)
            ch = obj.PIDSettings.Xe129Lock.index;
            
            filterOrder = obj.ziSlave.getPIDFilterOrder(ch);
            filterBW    = obj.ziSlave.getPIDFilterBW(ch);
            
            center  = obj.ziSlave.getPIDCenter(ch);
            pVal    = obj.ziSlave.getPValue(ch);
            iVal    = obj.ziSlave.getIValue(ch);
            dVal    = obj.ziSlave.getDValue(ch);  
            DlimVal = obj.ziSlave.getDLimValue(ch);
            rateVal = obj.ziSlave.getRateValue(ch);
        end
        function [center, pVal, iVal, dVal, DlimVal, rateVal, filterOrder, filterBW] = getXe131LockingParameter(obj)
            ch = obj.PIDSettings.Xe131Lock.index;
            
            filterOrder = obj.ziSlave.getPIDFilterOrder(ch);
            filterBW    = obj.ziSlave.getPIDFilterBW(ch);
            
            center  = obj.ziSlave.getPIDCenter(ch);
            pVal    = obj.ziSlave.getPValue(ch);
            iVal    = obj.ziSlave.getIValue(ch);
            dVal    = obj.ziSlave.getDValue(ch);  
            DlimVal = obj.ziSlave.getDLimValue(ch);
            rateVal = obj.ziSlave.getRateValue(ch);
        end
    end    
    
    %% Gyrocope Control
    methods
    end
    
    
    %%
    methods (Access = private)
        function obj = change_settings(obj, mainCat, mainTag, subTag, value)
            p=inputParser();
            catCheck = @(x) ismember(x, {'CurrentSettings', 'ModSettings', 'DemodSettings', 'PIDSettings', 'Conversions'});
            mainTagCheck = @(x) ismember(x, fieldnames(obj.(mainCat)));
            subTagCheck = @(x) ismember(x, fieldnames(obj.(mainCat).(mainTag)));
            p.addRequired('mainCat', catCheck);
            p.addRequired('mainTag', mainTagCheck);
            p.addRequired('subTag', subTagCheck);
            p.addRequired('value');
            p.parse(mainCat, mainTag, subTag, value);
            
            switch mainCat
                case 'CurrentSettings'
                    obj.CurrentSettings.(mainTag).(subTag) = value;
                case 'ModSettings'
                    obj.ModSettings.(mainTag).(subTag) = value;
                case 'DemodSettings'
                    obj.DemodSettings.(mainTag).(subTag) = value;
                case 'PIDSettings'
                    obj.PIDSettings.(mainTag).(subTag) = value;
                case 'Conversions'
                    obj.Conversions.(mainTag).(subTag) = value;
            end
        end
        function obj = disp_settings(obj, mainCat, mainTag, subTag)
            if nargin == 2                 
                if ismember(mainCat, {'CurrentSettings', 'ModSettings', 'DemodSettings', 'PIDSettings', 'Conversions'})
                    mainTags = fieldnames(obj.(mainCat));
                    fprintf('\n%s has %d main Tags:\n=================================\n\n', mainCat, length(mainTags));
                    
                    for k = 1:length(mainTags)
                        fprintf(2, '  %s.%s\n\n', mainCat, mainTags{k});
                        disp(struct2table(obj.(mainCat).(mainTags{k})));
                        fprintf('\n');
                    end
                else
                    error('Input field ''%s'' not found.', mainCat);
                end
            elseif nargin == 3
                if ismember(mainTag, fieldnames(obj.(mainCat)))
                    fprintf('\n');
                    fprintf('  %s.%s\n\n', mainCat, mainTag);
                    disp(struct2table(obj.(mainCat).(mainTag)));
                    fprintf('\n');
                else
                    error('Input field ''%s'' not found.', mainTag);
                end
            elseif nargin == 4
                if ismember(subTag, fieldnames(obj.(mainCat).(mainTag)))
                    fprintf('\n');
                    fprintf('  %s.%s.%s = \n', mainCat, mainTag, subTag);
                    disp(obj.(mainCat).(mainTag).(subTag));
                    fprintf('\n');
                else
                    error('Input field ''%s'' not found.', subTag);
                end
            end
        end
    end
end

